#include <iostream>
#include <vector>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>

#include "apidemo.h"

JzAGVApi::JzAGVApi(std::string host, short port)
{
    m_Host = host;
    m_Port = port;
    m_Sock = -1;
}

JzAGVApi::~JzAGVApi()
{
    if (m_Sock > 0)
    {
        close(m_Sock);
    }
}

int JzAGVApi::Connect()
{
    struct sockaddr_in serv_addr;

    char buffer[256];
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "ERROR opening socket" << std::endl;
    }
    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(m_Port);
    if(inet_pton(AF_INET, m_Host.c_str(), &serv_addr.sin_addr) <=0)
    { 
        std::cerr << "inet_pton error" << std::endl;
        return -1; 
    } 
    if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
    {
        std::cerr << "ERROR connecting" << std::endl;
        return -1;
    }

    m_Sock = sockfd;
    return 0;

}

api::Response* JzAGVApi::SendMessage(api::Request &req)
{
    if (m_Sock < 0)
    {
        std::cout << "no connect" << std::endl;
        return NULL;
    }

    api::ApiMessage msg;
    msg.set_type(api::MSG_REQ);
    // FIXME: req will be free by msg
    msg.set_allocated_req(new api::Request(req));
    
    std::string data;
    if (!msg.SerializeToString(&data))
    {
        std::cerr << "serialize error" << std::endl;
        return NULL;
    }
    if (Send(data) < 0)
    {
        std::cerr << "write error" << std::endl;
        return NULL;
    }

    std::string resp_data;
    if (Recv(resp_data) < 0)
    {
        std::cerr << "read error" << std::endl;
        return NULL;
    }

    api::ApiMessage resp_msg;
    if (!resp_msg.ParseFromString(resp_data))
    {
        std::cerr << "invalid resp data" << std::endl;
        return NULL;
    }

    return resp_msg.release_resp();
}

int JzAGVApi::Send(const std::string &data)
{
    int len = data.length();
    ssize_t written = write(m_Sock, &len, 4);
    if (written != 4)
    {
        return -1;
    }
    written = write(m_Sock, data.c_str(), data.length());
    if (written != data.length())
    {
        return -1;
    }
    return 0;
}

int JzAGVApi::Recv(std::string &data)
{
	unsigned int len(0);
	if (read(m_Sock, &len, 4) != 4)
	{
		std::cerr << "read error" << std::endl;
		return -1;
	}
	data.reserve(len);
    data.resize(len);

	int rbytes = 0;
	int rv;
	do {
		rv = read(m_Sock, &data[rbytes], len - rbytes);
		if( !rv ) {
			// End of File / Stream
            std::cerr << "closed by peer" << std::endl;
			break;
		}
		if( 0 > rv ) {
			if( EINTR == errno ) {
				// signal interrupted read... restart
                std::cout << "interrupted, conintue" << std::endl;
				continue;
			}
			if( EAGAIN == errno
					|| EWOULDBLOCK == errno ) {
				// file / socket is in nonblocking mode and
				// no more data is available.
                std::cerr << "block or again" << std::endl;
				break;
			}
			// some critical error happened. Deal with it!
			break;
		}
		rbytes += rv;
	} while(rbytes < len);

    if (rbytes < len)
    {
        std::cerr << "recv: no enough data" << std::endl;
        data = "";
        return -1;
    }
    return 0;

}

void JzAGVApi::GetRobots(std::vector<unsigned int> &ids)
{
    api::Request req;
    req.set_type(api::Request_RequestType_REQ_ROBOT_INFO);
    for (std::vector<unsigned int>::iterator it = ids.begin();
            it != ids.end(); ++it)
    {
        req.add_robot_ids(*it);
    }

    api::Response *resp = SendMessage(req);
    if (resp != NULL)
    {
        std::cout << "robot info: " << resp << std::endl;
    }
    else
    {
        std::cout << "robot info: nothing" << std::endl;
    }
}

int JzAGVApi::CreateMission(std::string uuid)
{
    api::Mission *m = new api::Mission();
    m->set_ref_uuid(uuid);
    m->set_priority(1); // 0-5(高优先级)
    //m->set_src("third party");
    //m->set_description("go go go");
    api::Mission_MissionStep *s = m->add_steps();
    s->set_target(9);
    s->set_map_name("Baiyun");
    s->set_action("load");

    api::Request req;
    req.set_type(api::Request_RequestType_REQ_MISSION_NEW);
    req.set_allocated_mission(m);

    api::Response *resp = SendMessage(req);
    if (resp == NULL)
    {
        std::cout << "failed to create mission" << std::endl;
        return 0;
    }

    std::cout << "new mission id: " << resp->mission().id() << std::endl;
    return resp->mission().id();
}

void JzAGVApi::GetMission(int missionID)
{
    api::Request req;
    req.set_type(api::Request_RequestType_REQ_MISSION_INFO);
    req.set_mission_id(missionID);
    
    api::Response *resp = SendMessage(req);
    if (resp == NULL)
    {
        std::cout << "failed to get mission" << std::endl;
        return;
    }

    std::cout << "success get mission" << std::endl;
}

void JzAGVApi::GetMissionByUUID(std::string uuid)
{
    api::Request req;
    req.set_type(api::Request_RequestType_REQ_MISSION_INFO);
    req.set_mission_uuid(uuid);
    
    api::Response *resp = SendMessage(req);
    if (resp == NULL)
    {
        std::cout << "failed to get mission" << std::endl;
        return;
    }

    std::cout << "success get mission" << std::endl;
}

int JzAGVApi::CancelMission(std::string uuid)
{
    api::MissionCommand *cmd = new api::MissionCommand();
    cmd->set_cmd(api::MissionCommand_CommandType_MCMD_CANCEL);
    cmd->set_mission_uuid(uuid);

    api::Request req;
    req.set_type(api::Request_RequestType_REQ_MISSION_COMMAND_NEW);
    req.set_allocated_mscmd(cmd);

    api::Response *resp = SendMessage(req);
    if (resp == NULL)
    {
        std::cout << "failed to cancel mission" << std::endl;
        return 0;
    }

    std::cout << "success sending 'cancel mission', cmd_id: " << resp->mscmd().id() << std::endl;
    return resp->mscmd().id();
}

void JzAGVApi::CheckIfCanceled(int cmdID)
{
    api::Request req;
    req.set_type(api::Request_RequestType_REQ_MISSION_COMMAND_INFO);
    req.set_mission_command_id(cmdID);

    api::Response *resp = SendMessage(req);
    if (resp == NULL)
    {
        std::cout << "failed to check 'cancel command'" << std::endl;
        return;
    }

    std::cout << "cancel result: " << resp->mscmd().result() << std::endl;
}
